// 
//  Copyright © 2008, 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as
//  published by the Free Software Foundation, either version 3 of the
//  License, or (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
// 
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// 

using System;

using Gnu.Inet.Encoding;

using Galaxium.Protocol.Xmpp.Library.Utility;

namespace Galaxium.Protocol.Xmpp.Library
{
	public class JidEventArgs: EventArgs
	{
		public JabberID UID { get; private set; }
		public JidEventArgs (JabberID uid) { UID = uid; }
	}
	
	public class JabberID
	{
		private readonly string m_node;
		private readonly string m_domain;
		private readonly string m_resource;
		
		public string Node          { get {return m_node;            }}
		public string UnescapedNode { get {return Unescape (m_node); }}
		public string Domain        { get {return m_domain;          }}
		public string Resource      { get {return m_resource;        }}

		public bool IsBare {get {return m_resource == null;}}
		public bool IsFull {get {return m_resource != null;}}
			
		public static readonly JabberID Empty = new JabberID ();

		private JabberID ()
		{
			m_domain = String.Empty;
		}
		
		public JabberID (string source)
			:this (source, false)
		{
		}
		
		public JabberID (string source, bool escape)
		{
			if (String.IsNullOrEmpty (source))
				throw new ArgumentNullException ();

			string node = null;
			string resource = null;

			int slash = source.IndexOf ('/');
			if (slash >= 0) {
				resource = source.Remove (0, slash + 1);
				source = source.Remove (slash);
			}
			
			int at = source.LastIndexOf ('@');
			if (at >= 0) {
				node = source.Remove (at);
				source = source.Remove (0, at + 1);
			}

			if (String.IsNullOrEmpty (source))
				throw new ArgumentException ();

			m_domain = Stringprep.NamePrep (source);
			
			if (!String.IsNullOrEmpty (node) && escape)
				node = Escape (node);
			
			if (!String.IsNullOrEmpty (node))
				m_node = Stringprep.NodePrep (node);
			if (!String.IsNullOrEmpty (resource))
				m_resource = Stringprep.ResourcePrep (resource);
		}
		
		public JabberID (string node, string domain, string resource)
		{
			if (domain == null)
				throw new ArgumentNullException ();
			if (domain == String.Empty)
				throw new ArgumentException ();

			m_domain = Stringprep.NamePrep (domain);
			
			if (!String.IsNullOrEmpty (node))
				m_node = Stringprep.NodePrep (node);
			if (!String.IsNullOrEmpty (resource))
				m_resource = Stringprep.ResourcePrep (resource);
		}

				
		public JabberID Bare ()
		{
			if (Resource == null) return this;
			return new JabberID (m_node, m_domain, null);
		}
		
		public static bool operator== (JabberID jid1, JabberID jid2)
		{
			if ((jid1 as object) == (jid2 as object)) return true;
			if ((jid1 as object) == null || (jid2 as object) == null) return false;
			
			return (jid1.Node == jid2.Node) &&
				(jid1.Domain == jid2.Domain) && (jid1.Resource == jid2.Resource);
		}
		
		public static bool operator!= (JabberID jid1, JabberID jid2)
		{
			return (!(jid1 == jid2));
		}
		
		public override int GetHashCode ()
		{
			return (m_node ?? String.Empty).GetHashCode () ^
				(m_resource ?? String.Empty).GetHashCode () ^
					m_domain.GetHashCode ();
		}
		
		public override bool Equals (object o)
		{
			if (o is JabberID)
				return Equals ((JabberID) o);
			return false;
		}

		public bool Equals (JabberID jid)
		{
			return this == jid;
		}
		
		public override string ToString ()
		{
			return ToString (false);
		}
		
		public string ToString (bool unescape)
		{
			string str = String.Empty;
			if (!String.IsNullOrEmpty (m_node))
				str += (unescape ? Unescape (m_node) : m_node) + '@';
			str += m_domain;
			if (!String.IsNullOrEmpty (m_resource))
				str += '/' + m_resource;
			return str;
		}
		
		public static implicit operator string (JabberID jid)
		{
			if (jid == null) return null;
			else return jid.ToString ();
		}
		
		public static implicit operator JabberID (string str)
		{
			if (str == null) return null;
			else return new JabberID (str);
		}
		
		private static string Escape (string node)
		{
			node = node.Replace ("\\", "\\5c");
			node = node.Replace (" ", "\\20");
			node = node.Replace ("\"", "\\22");
			node = node.Replace ("&", "\\26");
			node = node.Replace ("'", "\\27");
			node = node.Replace ("/", "\\2f");
			node = node.Replace (":", "\\3a");
			node = node.Replace ("<", "\\3c");
			node = node.Replace (">", "\\3e");
			node = node.Replace ("@", "\\40");
			return node;
		}
		
		private static string Unescape (string node)
		{
			node = node.Replace ("\\20", " ");
			node = node.Replace ("\\22", "\"");
			node = node.Replace ("\\26", "&");
			node = node.Replace ("\\27", "'");
			node = node.Replace ("\\2f", "/");
			node = node.Replace ("\\3a", ":");
			node = node.Replace ("\\3c", "<");
			node = node.Replace ("\\3e", ">");
			node = node.Replace ("\\40", "@");
			node = node.Replace ("\\5c", "\\");
			return node;
		}
	}
}